package com.excilys.jnullsafe;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;

/**
 * TODO think about lists, iterators, etc...
 * 
 */
public class JNullSafe {

    public static <T> T wrap(T instance, Type instanceInterfaceType) {

        T nullSafeProxy;

        if (instanceInterfaceType instanceof Class<?>) {
            nullSafeProxy = buildNullSafeProxy(instance, (Class<?>) instanceInterfaceType);
        } else if (instanceInterfaceType instanceof ParameterizedType) {
            nullSafeProxy = buildNullSafeProxy(instance, (ParameterizedType) instanceInterfaceType);
        } else {
            throw new RuntimeException("type not handled: " + instanceInterfaceType);
        }

        return nullSafeProxy;
    }

    private static <T> T buildNullSafeProxy(T instance, Class<?> type) {

        NullSafeHandler handler = new NullSafeHandler(instance, type);

        return buildProxy(type, handler);

    }

    private static <T> T buildNullSafeProxy(T instance, ParameterizedType parameterizedInstanceInterface) {
        NullSafeHandler handler = new NullSafeHandler(instance, parameterizedInstanceInterface);

        Class<?> instanceInterface = (Class<?>) parameterizedInstanceInterface.getRawType();

        return buildProxy(instanceInterface, handler);
    }

    @SuppressWarnings("unchecked")
    private static <T> T buildProxy(Class<?> type, NullSafeHandler invocationHandler) {

        if (!type.isInterface()) {
            throw new UnsupportedOperationException("Can only wrap interfaces, not valid: " + type);
        }

        return (T) Proxy.newProxyInstance( //
                JNullSafe.class.getClassLoader(), //
                new Class[] { type }, //
                invocationHandler);
    }

    @SuppressWarnings("unchecked")
    public static <T> T unwrap(T instance) {

        NullSafeHandler handler = handlerFromProxy(instance);

        if (handler != null) {
            return (T) handler.getWrappedInstance();
        } else {
            return instance;
        }

    }

    public static boolean isNull(Object instance) {

        NullSafeHandler handler = handlerFromProxy(instance);

        if (handler != null) {
            return handler.isInstanceNull();
        } else {
            return instance == null;
        }

    }

    public static boolean refEquals(Object instance1, Object instance2) {

        NullSafeHandler handler1 = handlerFromProxy(instance1);
        NullSafeHandler handler2 = handlerFromProxy(instance2);

        if (handler1 != null) {
            instance1 = handler1.getWrappedInstance();
        }

        if (handler2 != null) {
            instance2 = handler2.getWrappedInstance();
        }

        return instance1 == instance2;
    }

    private static NullSafeHandler handlerFromProxy(Object instance) {
        if (instance instanceof Proxy) {
            InvocationHandler handler = Proxy.getInvocationHandler(instance);

            if (handler instanceof NullSafeHandler) {
                return (NullSafeHandler) handler;
            }
        }
        return null;
    }

}
